package com.wimcorp.magic.cards;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;

import org.apache.commons.lang.StringUtils;

import com.wimcorp.magic.abilities.Ability;
import com.wimcorp.magic.abilities.AbilityHolder;
import com.wimcorp.magic.abilities.trigger.AbilityTrigger;
import com.wimcorp.magic.behaviour.Behaviour;
import com.wimcorp.magic.behaviour.BehaviourHolder;
import com.wimcorp.magic.board.SummoningPlains;
import com.wimcorp.magic.color.Color;
import com.wimcorp.magic.color.ColorHolder;
import com.wimcorp.magic.cost.Cost;
import com.wimcorp.magic.cost.CostHolder;
import com.wimcorp.magic.exception.CostException;
import com.wimcorp.magic.players.Player;
import com.wimcorp.magic.spell.Castable;

@Entity
public abstract class PlayingCard implements Castable {

	private static long identifierCounter = 1;

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	@Transient
	private long identifier;

	// @Column(unique = true)
	private Long multiverseid;

	private String name;
	@Column(columnDefinition = "text")
	private String description;

	@Embedded
	private ColorHolder colorHolder = new ColorHolder();

	@Embedded
	private CostHolder costHolder = new CostHolder();

	@Embedded
	private BehaviourHolder behaviourHolder = new BehaviourHolder(this);

	@Embedded
	private AbilityHolder abilityHolder = new AbilityHolder();

	@Transient
	private PlayingCard meta;

	@Transient
	private Player player;
	
	@Transient
	private Set<Ability> abilityTargetObservers = new HashSet<Ability>();

	public PlayingCard() {
	}

	public PlayingCard(long multiverseid, String name, String description, Set<Color> set, Set<Behaviour> behaviours,
			Set<Ability> abilities, Cost... costs) {
		this(multiverseid, name, set, behaviours, abilities, costs);
		this.description = description;
	}

	public PlayingCard(long multiverseid, String name, Set<Color> set, Set<Behaviour> behaviours,
			Set<Ability> abilities, Cost... costs) {
		this.name = name;
		this.multiverseid = multiverseid;
		for (Color color : set) {
			this.colorHolder.addColor(color);
		}
		for (Behaviour behaviour : behaviours) {
			this.behaviourHolder.addBehaviour(behaviour);
		}
		for (Cost cost : costs) {
			costHolder.addCost(cost);
		}
		for (Ability ability : abilities) {
			abilityHolder.addAbility(ability);
		}
	}

	public PlayingCard(PlayingCard card) {
		this(card.getMultiverseid(), card.getName(), card.getDescription(), card.getColors(), card.getBehaviour(),
				new HashSet<Ability>());
		this.meta = card;
		identifier = identifierCounter++;
	}

	public Set<Behaviour> getBehaviour() {
		return behaviourHolder.getBehaviours();
	}

	public Set<Ability> getAbilities() {
		return abilityHolder.getAbilities();
	}

	public PlayingCard clone() {
		PlayingCard copy = copy(this);
		Set<Ability> clonedAbilities = new HashSet<>();
		for (Ability ability : this.getAbilities()) {
			ability.setCard(copy);
			Ability clonedAbility = ability.clone();
			//clonedAbility.setCard(copy);
			AbilityTrigger trigger = clonedAbility.getTrigger();
			AbilityTrigger clonedTrigger = trigger.clone();
			clonedTrigger.setAbility(clonedAbility);
			clonedAbility.setTrigger(clonedTrigger);
			clonedAbilities.add(clonedAbility);
		}
		copy.removeAllAbilities();
		copy.addAbilities(clonedAbilities);
		for (Cost cost : this.getCosts()) {
			Cost clonedCost = cost.clone();
			clonedCost.setCastable(copy);
			copy.addCost(clonedCost);
		}
		return copy;
	}

	public PlayingCard copy() {
		PlayingCard copy = copy(this.getMeta());
		Set<Ability> clonedAbilities = new HashSet<>();
		for (Ability ability : this.getMeta().getAbilities()) {
			Ability clonedAbility = ability.clone();
			clonedAbility.setCard(copy);
			AbilityTrigger trigger = clonedAbility.getTrigger();
			AbilityTrigger clonedTrigger = trigger.clone();
			clonedTrigger.setAbility(clonedAbility);
			clonedAbility.setTrigger(clonedTrigger);
			clonedAbilities.add(clonedAbility);
		}
		copy.removeAllAbilities();
		copy.addAbilities(clonedAbilities);
		for (Cost cost : this.getMeta().getCosts()) {
			Cost clonedCost = cost.clone();
			clonedCost.setCastable(copy);
			copy.addCost(clonedCost);
		}
		return copy;
	}

	protected abstract PlayingCard copy(PlayingCard original);

	public abstract String getType();

	public void addCost(Cost cost) {
		this.costHolder.addCost(cost);
	}

	public void removeCost(Cost cost) {
		this.costHolder.removeCost(cost);
	}

	public void addBehaviour(Behaviour behaviour) {
		this.behaviourHolder.addBehaviour(behaviour);
	}

	public void removeBehaviour(Behaviour behaviour) {
		this.behaviourHolder.removeBehaviour(behaviour);
	}

	public boolean hasBehaviour(Behaviour behaviour) {
		return this.behaviourHolder.hasBehaviour(behaviour);
	}

	public void addAbility(Ability ability) {
		this.abilityHolder.addAbility(ability);
	}

	public void removeAbility(Ability ability) {
		this.abilityHolder.removeAbility(ability);
	}

	public void addAbilities(Set<Ability> abilities) {
		this.abilityHolder.addAbilities(abilities);

	}

	public void removeAllAbilities() {
		this.abilityHolder.removeAllAbilities();

	}

	public boolean hasTrigger(AbilityTrigger trigger) {
		return this.abilityHolder.hasTrigger(trigger);
	}

	public Set<Ability> getAbilities(AbilityTrigger trigger) {
		return this.abilityHolder.getAbilities(trigger);
	}

	public void registerAbilities() {
		this.abilityHolder.registerAbilities();
	}

	public void unRegisterAbilities() {
		this.abilityHolder.unRegisterAbilities();

	}

	public void cast() {
		getPlayer().getSummoningPlaines().playCard(this);
	}

	public void payCost() throws CostException{
		this.costHolder.payCost();
	}

	public void graveyard() {
		unsummon();
		getSummoningPlains().moveToGraveyard(this);
	}

	public void sacrifice() {
		unsummon();
		getSummoningPlains().moveToGraveyard(this);
	}

	public void unsummon() {
		getSummoningPlains().unsummon(this);
	}

	public void exile() {
		getSummoningPlains().unsummon(this);
		getSummoningPlains().moveToExile(this);
	}

	public String toString() {
		StringBuilder card = new StringBuilder("id : " + identifier);
		card.append("\n");
		card.append(getCardDescription());
		if (abilityHolder.size() > 0) {
			card.append(abilityHolder);
			card.append("\n");
		}
		return card.toString();	
	}
	
	private String getCardDescription(){
		StringBuilder card = new StringBuilder();
		card.append(getName());
		card.append("\n");
		card.append(getType());
		card.append("\n");
		if (behaviourHolder.size() > 0) {
			card.append(behaviourHolder);
			card.append("\n");
		}
		if (StringUtils.isNotEmpty(description)) {
			card.append(description);
			card.append("\n");
		}
		if (costHolder.size() > 0 && StringUtils.isNotEmpty(costHolder.toString())) {
			card.append(costHolder);
			card.append("\n");
		}
		return card.toString();
	}
	
	@Override
	public String getCastDescription() {
		return getCardDescription();
	}
	
	

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Set<Color> getColors() {
		return colorHolder.getColors();
	}

	public List<Cost> getCosts() {
		return costHolder.getCosts();
	}

	public Cost[] getCostsArray() {
		return costHolder.getCosts().toArray(new Cost[getCosts().size()]);
	}

	public Long getMultiverseid() {
		return multiverseid;
	}

	public void setMultiverseid(Long multiverseid) {
		this.multiverseid = multiverseid;
	}

	public long getIdentifier() {
		return identifier;
	}

	public void setIdentifier(long identifier) {
		this.identifier = identifier;
	}

	public SummoningPlains getSummoningPlains() {
		return player.getSummoningPlaines();
	}

	public PlayingCard getMeta() {
		return meta;
	}

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	protected BehaviourHolder getBehaviourHolder() {
		return behaviourHolder;
	}

	public void registerAbilityTargetListener(Ability ability) {
		abilityTargetObservers.add(ability);
	}

	public void unRegisterAbilityTargetListener(Ability ability) {
		abilityTargetObservers.remove(ability);
	}
	
	public Set<Ability> getAbilityTargetObservers() {
		return abilityTargetObservers;
	}

	/*
	 * public void registerTappedListener(Ability ability) {
	 * getBehaviourHolder().registerCardTappedListener(ability); }
	 * 
	 * public void unRegisterTappedListener(Ability ability) {
	 * getBehaviourHolder().unRegisterCardTappedListener(ability); }
	 */

}
